/*global env: true */
"use strict";

var fs = require("jsdoc/fs");
var helper = require("jsdoc/util/templateHelper");
var logger = require("jsdoc/util/logger");
var path = require("jsdoc/path");
var taffy = require("taffydb").taffy;
var template = require("jsdoc/template");
var util = require("util");
var option = require("../conf.json");
var glsl = require("./glsl");
var htmlsafe = helper.htmlsafe;
var linkto = helper.linkto;
var resolveAuthorLinks = helper.resolveAuthorLinks;
var scopeToPunc = helper.scopeToPunc;
var hasOwnProp = Object.prototype.hasOwnProperty;

var data;
var view;

var outdir = env.opts.destination;

function find(spec) {
  return helper.find(data, spec);
}

function tutoriallink(tutorial) {
  return helper.toTutorial(tutorial, null, {
    tag: "em",
    classname: "disabled",
    prefix: "Tutorial: ",
  });
}

function getAncestorLinks(doclet) {
  return helper.getAncestorLinks(data, doclet);
}

function hashToLink(doclet, hash) {
  if (!/^(#.+)/.test(hash)) {
    return hash;
  }

  var url = helper.createLink(doclet);

  url = url.replace(/(#.+|$)/, hash);
  return '<a href="' + url + '">' + hash + "</a>";
}

function needsSignature(doclet) {
  var needsSig = false;

  // function and class definitions always get a signature
  if (
    doclet.kind === "function" ||
    doclet.kind === "class" ||
    doclet.kind === "module"
  ) {
    needsSig = true;
  }
  // typedefs that contain functions get a signature, too
  else if (
    doclet.kind === "typedef" &&
    doclet.type &&
    doclet.type.names &&
    doclet.type.names.length
  ) {
    for (var i = 0, l = doclet.type.names.length; i < l; i++) {
      if (doclet.type.names[i].toLowerCase() === "function") {
        needsSig = true;
        break;
      }
    }
  }

  return needsSig;
}

function addSignatureParams(f) {
  var params = helper.getSignatureParams(f, "optional");

  f.signature = (f.signature || "") + "(" + params.join(", ") + ")";
}

function addSignatureReturns(f) {
  var returnTypes = helper.getSignatureReturns(f);

  f.signature = '<span class="signature">' + (f.signature || "") + "</span>";

  if (returnTypes.length) {
    f.signature +=
      ' &rarr; <span class="type-signature returnType">' +
      (returnTypes.length ? returnTypes.join("|") : "") +
      "</span>";
  }
}

function addSignatureTypes(f) {
  var types = helper.getSignatureTypes(f);

  f.signature =
    (f.signature || "") +
    '<span class="type-signature">' +
    (types.length ? " : " + types.join("|") : "") +
    "</span>";
}

function addAttribs(f) {
  var attribs = helper.getAttribs(f);

  if (f.deprecated) {
    attribs.push("deprecated");
  }

  if (attribs.length) {
    f.attribs = attribs
      .map(function(attrib) {
        return (
          '<span class="type-signature attribute-' +
          attrib +
          '">' +
          htmlsafe(attrib) +
          "</span> "
        );
      })
      .join("");
  }
}

function shortenPaths(files, commonPrefix) {
  Object.keys(files).forEach(function(file) {
    files[file].shortened = files[file].resolved
      .replace(commonPrefix, "")
      // always use forward slashes
      .replace(/\\/g, "/");
  });

  return files;
}

function getPathFromDoclet(doclet) {
  if (!doclet.meta) {
    return null;
  }

  return doclet.meta.path && doclet.meta.path !== "null"
    ? path.join(doclet.meta.path, doclet.meta.filename)
    : doclet.meta.filename;
}

function generate(title, docs, filename, resolveLinks) {
  resolveLinks = resolveLinks === false ? false : true;

  var docData = {
    filename: filename,
    title: title,
    docs: docs,
  };

  var outpath = path.join(outdir, filename),
    html = view.render("container.tmpl", docData);

  if (resolveLinks) {
    html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
  }
 
  try{
    fs.writeFileSync(outpath, html, "utf8");
  }catch(e){
    console.log("error",outpath,filename);
    console.log(e);
    console.log("------------具体的HTML--------------")
    console.log(html);
    console.log("------------具体的HTML End--------------")
    return false;
  }
  return true;
}

/**
 * Look for classes or functions with the same name as modules (which indicates that the module
 * exports only that class or function), then attach the classes or functions to the `module`
 * property of the appropriate module doclets. The name of each class or function is also updated
 * for display purposes. This function mutates the original arrays.
 *
 * @private
 * @param {Array.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to
 * check.
 * @param {Array.<module:jsdoc/doclet.Doclet>} modules - The array of module doclets to search.
 */
function attachModuleSymbols(doclets, modules) {
  var symbols = {};

  // build a lookup table
  doclets.forEach(function(symbol) {
    symbols[symbol.longname] = symbol;
  });

  return modules.map(function(module) {
    if (symbols[module.longname]) {
      module.module = symbols[module.longname];
      module.module.name =
        module.module.name.replace("module:", 'require("') + '")';
    }
  });
}

/**
 * Create the navigation sidebar.
 * @param {object} members The members that will be used to create the sidebar.
 * @param {array<object>} members.classes
 * @param {array<object>} members.externals
 * @param {array<object>} members.globals
 * @param {array<object>} members.mixins
 * @param {array<object>} members.modules
 * @param {array<object>} members.namespaces
 * @param {array<object>} members.tutorials
 * @param {array<object>} members.events
 * @return {string} The HTML for the navigation sidebar.
 */
function buildNav(members) {
  var nav = '<div id="ClassList" class="ClassList">',
    seen = {},
    hasClassList = false,
    classNav = "",
    globalNav = "";

  staticsClassInfo(members);

  var items = members.modules
    .concat(members.classes)
    .concat(members.globals)
    .concat(members.namespaces)
    .sort(function(a, b) {
      return a.longname.toLowerCase().localeCompare(b.longname.toLowerCase());
    });

  var json = buildLevelJsonItems(items);

  const gen_tree = (childs) => {
    var innerhtml = ''
    for (let nodeKey in childs) {
      if (nodeKey == "jsonDocLevelTag")
        continue;
      let node = childs[nodeKey]
      if (nodeKey != "leafNodes") {
        innerhtml += `<details level-tag="${node.jsonDocLevelTag}">
                <summary class="tree-item">
                    ${nodeKey}
                </summary>`
        innerhtml += gen_tree(node)
        innerhtml += `</details>`
      }
      else {
        node.forEach(leafNode => {
          let m = leafNode.mate
          innerhtml += getClassLabel(m);
        })
      }
    }
    return innerhtml;
  }

  json.forEach(item => {
    nav += `<div id="tree" class="tree">` + gen_tree(item) + `</div>`;
  });
  nav += buildMarkdownFiles("Docs", "Build\\Documentation\\Docs");
  nav += "</div>";

  copyMainFrameHTML("Build\\Documentation", nav);

  return nav;
}

function buildLevelJsonItems(items) {
  const sortItems = items.sort((a, b) => {
    if (a.meta.filename < b.meta.filename) return -1;
    if (a.meta.filename > b.meta.filename) return 1;
    return 0;
  })
  let buildJson = [{}];
  let currentGroup = buildJson[0];
  const groupSize =10;// sortItems.length / 4;
  let count = 0;
  let lastRevalteName="";
  sortItems.forEach((item) => {
    var revaltePath = item.meta.filename.split("/");
    let currentObject = currentGroup;
    if (option.level <= 2 && revaltePath.length - 1 > option.level) {
      return;
    }
    for (let i = 0; i < revaltePath.length - 1; i++) {
      if (!currentObject[revaltePath[i]])
        currentObject[revaltePath[i]] = { jsonDocLevelTag: i };
      currentObject = currentObject[revaltePath[i]];
    }
    if (!currentObject["leafNodes"])
      currentObject["leafNodes"] = [];
    var leafNode = { name: item.name, jsonDocLevelTag: revaltePath.length - 1, mate: item };//
    currentObject["leafNodes"].push(leafNode);
    if(lastRevalteName!=revaltePath[0]){
      count++;
      lastRevalteName=revaltePath[0];
    }
    if (count >= groupSize) {
      buildJson.push({});
      currentGroup = buildJson[buildJson.length - 1];
      count = 0;
    }
  })
  return buildJson;
}

/**
 * 陈彪：把Doc下面的内容也写到最终的输出目录
 * @param {*} dirPath 
 * @param {*} resPath 
 * @returns 
 */
function buildMarkdownFiles(dirPath, resPath) {
  const Readme = require('jsdoc/readme');
  const fs = require('jsdoc/fs');
  const path = require('path');

  fs.mkdirSync(resPath, { recursive: true });

  const files = fs.readdirSync(dirPath);
  const mdFiles = [];
  files.forEach(file => {
    const filePath = path.join(dirPath, file);
    let resultFilePath = path.join(resPath, file.substr(0, file.length - 3) + ".html");
    if (file.endsWith("imgs") || file.endsWith("css")) {
      resultFilePath = path.join(resPath, file);
      copyDirectorySync(filePath, resultFilePath);
      return;
    }
    if (file.endsWith(".md") == false) {
      return;
    }

    mdFiles.push(resultFilePath)
    var readme = new Readme(filePath);
    try {
      var head = '<meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><link href="css/index.css" rel="stylesheet">';

      var html = '<html><head>' + head + '</head><body>' + readme.html + "</body></html>";
      fs.writeFileSync(resultFilePath, html);
      console.log('文件写入成功！');
    } catch (err) {
      return console.error('写入文件失败:', err);
    }
  });
  return updateIndexHTML(resPath, mdFiles);
}
/**
 * 陈彪：把Doc下面的内容也写到最终的输出目录，附带图片等内容
 * @param {*} src 
 * @param {*} dest 
 */
function copyDirectorySync(src, dest) {
  const fs = require('jsdoc/fs');
  const path = require('path');
  console.log("要复制目录", src, "目标目录", dest);
  try {
    // 确保目标目录存在  
    fs.mkdirSync(dest, { recursive: true });

    // 读取源目录的内容  
    const files = fs.readdirSync(src);

    // 遍历文件和子目录  
    for (const file of files) {
      const srcFile = path.join(src, file);
      const destFile = path.join(dest, file);

      const stats = fs.statSync(srcFile);
      console.log("Find", srcFile, "目标文件", destFile, stats.isDirectory());
      if (stats.isDirectory()) {
        // 如果是目录，递归调用  
        copyDirectorySync(srcFile, destFile);
      } else {
        // 如果是文件，复制文件  
        console.log(`正在复制文件: ${srcFile} 到 ${destFile}`);
        fs.copyFileSync(srcFile, dest);
      }
    }
  } catch (err) {
    console.error('复制目录失败:', err);
  }
}
/**
 * 陈彪：把这些内容写到Index文件中
 * @param {*} resPath 
 * @param {*} mdFiles 
 * @returns 
 */
function updateIndexHTML(resPath, mdFiles) {
  var files = "<div class='tree'>";
  files = files + '<details open="" level-tag="0"><summary class="tree-item">文档</summary>';
  for (var i = 0; i < mdFiles.length; i++) {
    let a1 = mdFiles[i].substr(mdFiles[i].indexOf(resPath) + resPath.lastIndexOf("\\"));
    //console.log("resPath", a1)
    files = files + "<li><details><summary class='tree-item'><a href='." + a1 + "'>" + mdFiles[i].substr(mdFiles[i].lastIndexOf("\\") + 1) + "</a></summary></details></li>";
  }
  files = files + "</details></div>";

  // Add GLSL shader documentation section
  files =files +"<div class='tree'>";
  files = files + '<details open="" level-tag="0"><summary class="tree-item">GLSL Shaders</summary>';
  files = files + '<li><details><summary class="tree-item"><a href="./glsl/index.html" target="_blank">Shanders</a></summary></details></li>';
  files = files + '</details>';
  files = files + "</details></div>";

  return files;
}

/**
 * 陈彪，创建一个主要的类，这个类是独立窗口，不需要那么复杂
 */
function copyMainFrameHTML(src, nav) {
  const fs = require('jsdoc/fs');
  const path = require('path');
  const srcFile = path.join(src, "html/main.html");
  const destFile = path.join(src, "main.html");

  var content = fs.readFileSync(srcFile, env.opts.encoding);
  content = content.replace('<div class="nav"></div>', '<div class="nav">' + nav + "</div>");

  fs.writeFileSync(destFile, content);
}

const agClasses = {};
const devLevel = {};
const codeZizhuLv = { "classes": 0, "agClasses": 0, "modules": 0 }
/**
 * 陈彪，对类的信息进行统计和标记，比如是奥格的类，还有开发等级
 */
function staticsClassInfo(members) {
  var classes = members.classes;
  var globals = members.globals;
  codeZizhuLv.agClasses = 0;
  codeZizhuLv.classes = classes.length;
  codeZizhuLv.modules = members.modules.length;
  codeZizhuLv.globals = members.globals.length;

  for (var i = 0; i < classes.length; i++) {
    if (classes[i].author && classes[i].author.length > 0) {
      for (var j = 0; j < classes[i].author.length; j++) {
        if (classes[i].author[j].indexOf('augur') >= 0) {
          agClasses[classes[i].longname] = true;
          codeZizhuLv.agClasses++;
          break;
        }
      }
    }
    if (classes[i].developLevel != undefined) {
      devLevel[classes[i].longname] = classes[i].developLevel.trimStart().substr(0, 1);
    }
  }

  for (var i = 0; i < globals.length; i++) {
    if (globals[i].author && globals[i].author.length > 0) {
      for (var j = 0; j < globals[i].author.length; j++) {
        if (globals[i].author[j].indexOf('augur') >= 0) {
          agClasses[globals[i].longname] = true;
          codeZizhuLv.agClasses++;
          break;
        }
      }
    }
    if (globals[i].developLevel != undefined) {
      devLevel[globals[i].longname] = globals[i].developLevel.trimStart().substr(0, 1);
    }
  }
}
/**
 * 陈彪，对于标记的类，显示其中的标记
 * @param {*} m 
 * @returns 
 */
function getClassLabel(m) {
  let info = (agClasses[m.longname] ? "<i class='ag'>AG</i>" : "") +
    (devLevel[m.longname] ? "<i class='developLevel'>" + devLevel[m.longname] + "</i>" : "");
  return '<li data-name="' +
    m.name +
    '">' + `<details><summary class="tree-item">` +
    info + linkto(m.longname, m.name) + `</summary></details>` + "</li>"
}

function writeStaticsToIndexFile(src) {
  const fs = require('jsdoc/fs');
  const path = require('path');
  const srcFile = path.join(src, "index.html");

  var content = fs.readFileSync(srcFile, env.opts.encoding);
  content = content.replace('{ClassNum}', codeZizhuLv.classes);
  content = content.replace('{Modules}', codeZizhuLv.modules);
  content = content.replace('{AgClassNum}', codeZizhuLv.agClasses);

  fs.writeFileSync(srcFile, content);
}


/**
    @param {TAFFY} taffyData See <http://taffydb.com/>.
    @param {object} opts
    @param {Tutorial} tutorials
 */
exports.publish = function(taffyData, opts, tutorials) {

  data = taffyData;

  var conf = env.conf.templates || {};
  conf["default"] = conf["default"] || {};

  var templatePath = opts.template;
  view = new template.Template(templatePath + "/tmpl");

  // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
  // doesn't try to hand them out later
  var indexUrl = helper.getUniqueFilename("index");
  // don't call registerLink() on this one! 'index' is also a valid longname

  var globalUrl = helper.getUniqueFilename("global");
  helper.registerLink("global", globalUrl);

  // set up templating
  view.layout = "layout.tmpl";

  // set up tutorials for helper
  helper.setTutorials(tutorials);

  data = helper.prune(data);
  data.sort("longname, version, since");
  helper.addEventListeners(data);

  var sourceFiles = {};
  var sourceFilePaths = [];
  data().each(function(doclet) {
    doclet.attribs = "";

    doclet.longname = doclet.longname.replace(/^module:/, "");
    if (doclet.memberof)
      doclet.memberof = doclet.memberof.replace(/^module:/, "");

    if (doclet.examples) {
      doclet.examples = doclet.examples.map(function(example) {
        var caption, code;

        if (
          example.match(
            /^\s*<caption>([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i,
          )
        ) {
          caption = RegExp.$1;
          code = RegExp.$3;
        }

        return {
          caption: caption || "",
          code: code || example,
        };
      });
    }
    if (doclet.see) {
      doclet.see.forEach(function(seeItem, i) {
        doclet.see[i] = hashToLink(doclet, seeItem);
      });
    }

    // build a list of source files
    var sourcePath;
    if (doclet.meta) {
      sourcePath = getPathFromDoclet(doclet);
      sourceFiles[sourcePath] = {
        resolved: sourcePath,
        shortened: null,
      };
      if (sourceFilePaths.indexOf(sourcePath) === -1) {
        sourceFilePaths.push(sourcePath);
      }
    }
  });

  // update outdir if necessary, then create outdir
  var packageInfo = (find({ kind: "package" }) || [])[0];
  if (packageInfo && packageInfo.name) {
    outdir = path.join(outdir, packageInfo.name, packageInfo.version);
  }
  fs.mkPath(outdir);

  // copy the template's static files to outdir
  var fromDir = path.join(templatePath, "static");
  var staticFiles = fs.ls(fromDir, 3);

  staticFiles.forEach(function(fileName) {
    var toDir = fs.toDir(fileName.replace(fromDir, outdir));
    fs.mkPath(toDir);
    fs.copyFileSync(fileName, toDir);
  });

  if (sourceFilePaths.length) {
    sourceFiles = shortenPaths(sourceFiles, path.commonPrefix(sourceFilePaths));
  }
  data().each(function(doclet) {
    var url = helper.createLink(doclet);
    helper.registerLink(doclet.longname, url);

    // replace the filename with a shortened version of the full path
    var docletPath;
    if (doclet.meta) {
      docletPath = getPathFromDoclet(doclet);
      docletPath = sourceFiles[docletPath].shortened;
      if (docletPath) {
        doclet.meta.filename = docletPath;
        doclet.meta.sourceUrl = conf["sourceUrl"]
          .replace("{version}", process.env.BARIUM_VERSION)
          .replace("{filename}", docletPath);
        if (process.env.BARIUM_PACKAGES) {
          doclet.meta.package = process.env.BARIUM_PACKAGES.split(",").find(
            (package) => doclet.meta.sourceUrl.indexOf(package) > -1,
          );
        }
      }
    }
  });

  data().each(function(doclet) {
    var url = helper.longnameToUrl[doclet.longname];

    if (url.indexOf("#") > -1) {
      doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
    } else {
      doclet.id = doclet.name;
    }

    if (needsSignature(doclet)) {
      addSignatureParams(doclet);
      addSignatureReturns(doclet);
      addAttribs(doclet);
    }
  });

  // do this after the urls have all been generated
  data().each(function(doclet) {
    doclet.ancestors = getAncestorLinks(doclet);

    if (doclet.kind === "member") {
      addSignatureTypes(doclet);
      addAttribs(doclet);
    }

    if (doclet.kind === "constant") {
      addSignatureTypes(doclet);
      addAttribs(doclet);
      doclet.kind = "member";
    }
  });

  var members = helper.getMembers(data);
  // add template helpers
  view.find = find;
  view.linkto = linkto;
  view.resolveAuthorLinks = resolveAuthorLinks;
  view.tutoriallink = tutoriallink;
  view.htmlsafe = htmlsafe;

  // once for all
  view.nav = buildNav(members);


  attachModuleSymbols(
    find({ kind: ["class", "function"], longname: { left: "module:" } }),
    members.modules,
  );

  if (members.globals.length) {
    generate("Global", [{ kind: "globalobj" }], globalUrl);
  }

  // index page displays information from package.json and lists files
  var files = find({ kind: "file" }),
    packages = find({ kind: "package" });

  var origLayout = view.layout;
  view.layout = "indexLayout.tmpl";
  generate(
    "Index",
    packages
      .concat([
        {
          kind: "mainpage",
          readme: opts.readme,
          longname: opts.mainpagetitle ? opts.mainpagetitle : "Main Page",
        },
      ])
      .concat(files),
    indexUrl,
  );
  view.layout = origLayout;

  // set up the lists that we'll use to generate pages
  var classes = taffy(members.classes);
  var modules = taffy(members.modules);
  var namespaces = taffy(members.namespaces);
  var globals = taffy(members.globals);

  var typesJson = {};

  Object.keys(helper.longnameToUrl).forEach(function(longname) {
    var items = helper.find(classes, { longname: longname });

    if (!items.length) {
      items = helper.find(modules, { longname: longname });
    }

    if (!items.length) {
      items = helper.find(namespaces, { longname: longname });
    }

    if (!items.length) {
      items = helper.find(globals, { longname: longname });
    }

    if (items.length) {
      var title = items[0].name;
      var filename = helper.longnameToUrl[longname];
      //console.log("items--", items[0]);
      if(generate(title, items, filename)==false){
        console.log("generate error",title,longname)
      }

      var titleLower = title.toLowerCase();

      typesJson[titleLower] = typesJson[titleLower] || [];
      typesJson[titleLower].push(filename);

      var members = find({ kind: ["function", "member"], memberof: longname });
      members.forEach(function(member) {
        member = member.id;
        var memberLower = member.toLowerCase();
        var firstChar = memberLower.charAt(0);
        if (firstChar === "." || firstChar === "~") {
          memberLower = memberLower.substring(1);
        }

        typesJson[memberLower] = typesJson[memberLower] || [];
        typesJson[memberLower].push(filename + "#" + member);
      });
    }
  });

  fs.writeFileSync(outdir + "/types.txt", JSON.stringify(typesJson), "utf8");

  writeStaticsToIndexFile("Build\\Documentation");

  let res =glsl.getGlslContent("packages\\engine\\Source\\Shaders","Docs\\imgs\\Shaders");

  glsl.generateGlslDocs(res, view);

};
